package com.basker.pisces.orm.session;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.commons.lang3.ArrayUtils;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.CollectionUtils;

import com.basker.pisces.core.IDataContract;
import com.basker.pisces.core.enums.DataObjectState;
import com.basker.pisces.core.helper.DataContractHelper;
import com.basker.pisces.core.iterate.DataCollectionIterateEvent;
import com.basker.pisces.core.iterate.DataObjectIterateEvent;
import com.basker.pisces.core.iterate.DataObjectIterator;
import com.basker.pisces.core.iterate.IDataObjectIterateHandler;
import com.basker.pisces.core.meta.ICollectionField;
import com.basker.pisces.core.meta.IDataField;
import com.basker.pisces.core.meta.IDataObjectField;
import com.basker.pisces.core.meta.IDataObjectMeta;
import com.basker.pisces.core.meta.IReferenceField;
import com.basker.pisces.core.meta.annotation.TimestampField.TimestampType;
import com.basker.pisces.exception.ExceptionFactory;
import com.basker.pisces.orm.data.meta.DataSetMeta;
import com.basker.pisces.orm.data.meta.FieldMeta;
import com.basker.pisces.orm.data.meta.TableMeta;
import com.basker.pisces.orm.event.DataObjectWriteEvent;
import com.basker.pisces.orm.event.DataObjectWriteEvent.DataRowWriteEvent;
import com.basker.pisces.orm.event.DataObjectWriteEvent.FieldChangeEvent;
import com.basker.pisces.orm.event.DataObjectWriteEvent.WriteAction;
import com.basker.pisces.orm.execute.ISqlExecutor;
import com.basker.pisces.orm.parser.ISqlParser;
import com.basker.pisces.orm.statement.BatchWriteStatement;
import com.basker.pisces.orm.statement.DataTableWriteCommandWrapper;
import com.basker.pisces.orm.statement.FKFieldObject;
import com.basker.pisces.orm.statement.FieldObject;
import com.basker.pisces.orm.statement.InsertCommand;
import com.basker.pisces.orm.statement.PKFieldObject;
import com.basker.pisces.orm.statement.UpdateCommand;
import com.basker.pisces.tx.TransactionHelper;

/**
 * 负责对象的保存
 *
 * <li>保存前会分析每一个对象，确定其是insert、update、delete或者不理会；
 * <li>对于需要update的对象，只会set其变更的字段值
 *
 * @author hangwen
 */
public class DataWriter {

    /**
     * 记录执行保存操作时的时间点，确保一次保存的数据的时间戳值是一致的
     */
    private Long saveTimestamp;

    /**
     * 对象元信息
     */
    private IDataObjectMeta objectMeta;
    /**
     * 对象元信息对应的数据集元信息
     */
    private DataSetMeta dataSetMeta;

    /**
     * sql语句解析器
     */
    private ISqlParser sqlParser;

    /**
     * sql执行器
     */
    private ISqlExecutor sqlExecutor;

    /**
     * 事务管理器
     */
    private TransactionTemplate transactionTemplate;

    /**
     * 负责将制定id从缓存中剔除
     */
    private Consumer<Object[]> cacheRemoveHandler;

    /**
     * 确保按表的插入顺序迭代
     */
    private Map<String/* tableName */, DataTableWriteCommandWrapper> writeCommands = new LinkedHashMap<>();

    private DataObjectSaveAnalyzer analyzer = new DataObjectSaveAnalyzer();

    private OptimisticLockingValidator optimisticLockingValidator = OptimisticLockingValidator.INSTANCE;

    private List<DataRowWriteEvent> rowWriteEvents = new ArrayList<>();

    public DataWriter(IDataObjectMeta objectMeta, ISqlParser sqlParser, ISqlExecutor sqlExecutor,
                      TransactionTemplate transactionTemplate, Consumer<Object[]> cacheRemoveHandler) {
        this.objectMeta = objectMeta;
        this.dataSetMeta = DataSetMeta.create(objectMeta);

        this.sqlParser = sqlParser;
        this.sqlExecutor = sqlExecutor;
        this.transactionTemplate = transactionTemplate;
        this.cacheRemoveHandler = cacheRemoveHandler;
    }

    /**
     * 删除整个对象，包括其扩展表、子表
     *
     * @param ids
     */
    public void delete(Object[] ids) {
        // 只读对象不可保存，否则抛出异常
        if (this.objectMeta.isReadOnly()) {
            throw ExceptionFactory.createRuntimeException("pisces-orm.DataWriter.delete-001", "{0} is readOnly!",
                    this.objectMeta.getName());
        }

        List<Object> idList = Arrays.asList(ids);

        List<BatchWriteStatement> deleteStatements = this.sqlParser.getDeleteStatements(this.dataSetMeta, idList);
        this.addRowDeleteEvent(this.dataSetMeta.getRootTableMeta(), idList);

        this.executeWriteStatements(deleteStatements, ids);
    }

    /**
     * 保存对象
     *
     * @param dataObjects
     * @return
     * @throws OptimisticLockingFailureException
     */
    public Object[] save(Object[] dataObjects) throws OptimisticLockingFailureException {
        // 只读对象不可保存，否则抛出异常
        if (this.objectMeta.isReadOnly()) {
            throw ExceptionFactory.createRuntimeException("pisces-orm.DataWriter.save-001", "{0} is readOnly!",
                    this.objectMeta.getName());
        }

        // 只读对象不可保存，否则抛出异常
        for (Object dataObject : dataObjects) {
            DataContractHelper.assertNotReadOnly(dataObject);
        }

        // 分析对象,并创建语句
        this.analysisDataObjects(dataObjects);

        // 获取批量更新语句，迭代执行
        List<BatchWriteStatement> writeStatements = this.genBatchWriteStatements();
        if (CollectionUtils.isEmpty(writeStatements)) {
            return ArrayUtils.EMPTY_OBJECT_ARRAY;
        }

        // 获取新增和更新的Id
        Object[] addedIds = this.getAddedObjectIds(objectMeta, dataObjects);
        Object[] modifiedIds = this.getModifiedObjectIds(objectMeta, dataObjects);

        Object[] ids = ArrayUtils.addAll(addedIds, modifiedIds);

        this.executeWriteStatements(writeStatements, ids);

        return ids;

    }

    DataObjectWriteEvent getWriteEvent() {
        if (CollectionUtils.isEmpty(this.rowWriteEvents)) {
            return null;
        }

        return new DataObjectWriteEvent(this, this.rowWriteEvents);
    }

    private void addDeleteCommand(TableMeta tableMeta, Collection<Object> deletePks) {
        DataTableWriteCommandWrapper commandWrapper = this.getDataTableWriteStatementWrapper(tableMeta);

        commandWrapper.addDeletePkValues(deletePks);
    }

    private void addInsertCommand(Object parentObject, Object dataObject, TableMeta tableMeta) {
        // 设置主键
        FieldMeta pkFieldMeta = tableMeta.getPkFieldMeta();
        PKFieldObject pkFieldObject = new PKFieldObject(pkFieldMeta, dataObject);
        InsertCommand insertCommand = new InsertCommand(pkFieldObject);

        // 设置外键
        FieldMeta fkFieldMeta = tableMeta.getFkFieldMeta();
        if (fkFieldMeta != null && parentObject != null) {
            FieldObject fkfieldObject = new FKFieldObject(fkFieldMeta, parentObject);
            insertCommand.addField(fkfieldObject);
        }

        final boolean insertNullValue = false;// 一个预留的开关
        boolean hasUnNullValue = false;

        // 设置需要插入的字段
        for (FieldMeta insertField : tableMeta.getInsertFields()) {
            if (insertField == pkFieldMeta) {
                // 如果当前是子表,主键字段在新增时候需要加入
                // 如果是扩展表，主键字段和外键字段是一致的，所以不会加入addField方法内部会控制好，因此，如果其他字段都没有值，扩展表是不会插入行的
                insertCommand.addField(pkFieldObject);
            } else {
                Object value = insertField.getDataField().getValue(dataObject);
                if (value != null) {
                    // 只要存在一个非null的字段，当前命令就是有效的，换言之，如果只有一个主键有值的话，不会执行命令
                    hasUnNullValue = true;
                    insertCommand.addField(new FieldObject(insertField, value));
                } else if (insertNullValue) {
                    insertCommand.addField(new FieldObject(insertField, value));
                }
            }
        }

        if (insertCommand.getInsertFields().size() > 1 && hasUnNullValue) {
            DataTableWriteCommandWrapper commandWrapper = this.getDataTableWriteStatementWrapper(tableMeta);
            commandWrapper.addInsertCommand(insertCommand);
        }

    }

    private void addRowDeleteEvent(TableMeta tableMeta, List<Object> deletePkValues) {
        String tableName = tableMeta.getName();

        for (Object pkValue : deletePkValues) {
            DataRowWriteEvent rowEvent = new DataRowWriteEvent(tableName, pkValue, WriteAction.Delete);
            this.rowWriteEvents.add(rowEvent);
        }
    }

    private void addRowInsertEvent(TableMeta tableMeta, List<InsertCommand> insertCommands) {
        String tableName = tableMeta.getName();

        for (InsertCommand insertCommand : insertCommands) {
            FieldObject pkFieldObject = insertCommand.getPkFieldObject();
            Object pkValue = pkFieldObject.getValue();

            DataRowWriteEvent rowEvent = new DataRowWriteEvent(tableName, pkValue, WriteAction.Insert);
            this.rowWriteEvents.add(rowEvent);
        }
    }

    private void addRowUpdateEvent(TableMeta tableMeta, List<UpdateCommand> updateCommands) {
        String tableName = tableMeta.getName();

        for (UpdateCommand updateCommand : updateCommands) {
            FieldObject pkFieldObject = updateCommand.getPkFieldObject();
            Object pkValue = pkFieldObject.getValue();
            List<FieldChangeEvent> fieldChangeEvents = new ArrayList<>(updateCommand.getUpdateFields().size());

            for (FieldObject updateField : updateCommand.getUpdateFields()) {
                FieldChangeEvent fieldChangeEvent = new FieldChangeEvent(updateField.getName(), updateField.getValue(),
                        updateField.getOldValue());
                fieldChangeEvents.add(fieldChangeEvent);
            }

            DataRowWriteEvent rowEvent = new DataRowWriteEvent(tableName, pkValue, WriteAction.Update,
                    fieldChangeEvents);
            this.rowWriteEvents.add(rowEvent);
        }
    }

    private void addUpdateCommand(IDataObjectMeta objectMeta, Object dataObject, TableMeta tableMeta, Long oldTs) {
        if (tableMeta == null || dataObject == null) {
            return;
        }

        FieldObject pkFieldObject = null;

        // 对于扩展表 外键即主键
        if (tableMeta.isSubTable()) {
            FieldMeta fkFieldMeta = tableMeta.getFkFieldMeta();
            pkFieldObject = new FKFieldObject(fkFieldMeta, dataObject);
        } else {
            FieldMeta pkFieldMeta = tableMeta.getPkFieldMeta();
            pkFieldObject = new PKFieldObject(pkFieldMeta, dataObject);
        }

        UpdateCommand updateCommand = new UpdateCommand(pkFieldObject);

        // 需要更新的字段
        for (FieldMeta fieldMeta : tableMeta.getUpdateFields()) {
            // 值没有改变，不拼接到sql
            if (!objectMeta.isChanged(dataObject, fieldMeta.getDataField())) {
                continue;
            }

            Object value = fieldMeta.getDataField().getValue(dataObject);
            Object oldValue = objectMeta.getOldValue(dataObject, fieldMeta.getDataField());

            FieldObject fieldObject = new FieldObject(fieldMeta, value);
            fieldObject.setOldValue(oldValue);

            updateCommand.addField(fieldObject);

            // 设置先前的时间戳字段
            if (oldTs != null && fieldMeta.getDataField() == objectMeta.getTimestampField()) {
                FieldObject tsFieldObject = new FieldObject(fieldMeta, oldTs);
                updateCommand.setTimestampFieldObject(tsFieldObject);
            }
        }

        if (updateCommand.getUpdateFields().size() > 0) {
            DataTableWriteCommandWrapper commandWrapper = this.getDataTableWriteStatementWrapper(tableMeta);
            commandWrapper.addUpdateCommand(updateCommand);
        }
    }

    private void analysisDataCollection(ICollectionField collectionField, Object parentObject) {
        // 获取集合中删除的对象的id
        Collection<IDataContract> removedObjects = collectionField.getRemovedObjects(parentObject);
        if (CollectionUtils.isEmpty(removedObjects)) {
            return;
        }

        // 只读对象不考虑
        IDataObjectMeta itemObjectMeta = collectionField.getItemObjectMeta();
        if (itemObjectMeta.isReadOnly()) {
            return;
        }

        Collection<Object> deletePks = removedObjects.stream().map(o -> o.getPkValue()).collect(Collectors.toList());

        String tableName = itemObjectMeta.getTableName();
        String[] subTableNames = itemObjectMeta.getSubTableNames();

        // 将对象的扩展表删除
        if (!ArrayUtils.isEmpty(subTableNames)) {
            for (String subTableName : subTableNames) {
                TableMeta subTableMeta = this.dataSetMeta.getSubTableMeta(tableName, subTableName);
                this.addDeleteCommand(subTableMeta, deletePks);
            }
        }

        TableMeta tableMeta = this.dataSetMeta.getTableMeta(tableName);
        this.addDeleteCommand(tableMeta, deletePks);

        for (ICollectionField subCollectionField : itemObjectMeta.getCollectionFields()) {
            for (IDataContract removedObject : removedObjects) {
                Collection<Object> collection = subCollectionField.getValue(removedObject);

                if (CollectionUtils.isEmpty(collection)) {
                    continue;
                }

                collection.clear();
                this.analysisDataCollection(subCollectionField, removedObject);
            }
        }

    }

    /**
     * 逐表逐行迭代，分析每个对象，并创建对应的命令
     *
     * @param objectMeta
     * @param parentObject
     * @param dataObject
     */
    private void analysisDataObject(IDataObjectMeta objectMeta, Object parentObject, Object dataObject) {
        // 元信息只读，比如某个子表是只读的
        if (objectMeta.isReadOnly()) {
            return;
        }

        String tableName = objectMeta.getTableName();
        TableMeta tableMeta = this.dataSetMeta.getTableMeta(tableName);

        // 判断dataObj对象是新增还是修改，新增添加到Insert命令，修改添加到Update命令
        boolean isfromDB = objectMeta.isFromDB(dataObject);

        // 获取对象的状态，单单isfromDB只能判断对象是否从数据库读出，需要通过状态判断要不要更新
        DataObjectState state = objectMeta.getState(dataObject);
        // Modified或者NoDefinition时，对象需要更新
        boolean isModifyOrNoDefState = state.equals(DataObjectState.Modified)
                || state.equals(DataObjectState.NoDefinition);

        if (isfromDB) {
            if (isModifyOrNoDefState) {
                Long oldTs = this.refreshTimestamp(objectMeta, dataObject, isfromDB);
                this.addUpdateCommand(objectMeta, dataObject, tableMeta, oldTs);
            }
        } else {
            this.refreshTimestamp(objectMeta, dataObject, isfromDB);
            this.addInsertCommand(parentObject, dataObject, tableMeta);
        }

        // 对扩展表的新增Or更新
        String[] subTableNames = objectMeta.getSubTableNames();
        if (!ArrayUtils.isEmpty(subTableNames)) {
            for (String subTableName : subTableNames) {
                TableMeta subTableMeta = this.dataSetMeta.getSubTableMeta(tableName, subTableName);

                boolean subTableIsFromDb = objectMeta.isFromDB(dataObject, subTableName);
                if (subTableIsFromDb) {
                    if (isModifyOrNoDefState) {
                        addUpdateCommand(objectMeta, dataObject, subTableMeta, null);
                    }
                } else {
                    addInsertCommand(dataObject, dataObject, subTableMeta);
                }
            }
        }

    }

    private void analysisDataObjects(Object[] dataObjects) {
        // 设置时间戳字段
        this.saveTimestamp = this.getTimeStamp();

        DataObjectIterator iterator = new DataObjectIterator(dataObjects, this.objectMeta, analyzer);
        iterator.iterate();

        this.saveTimestamp = null;
    }

    /**
     * 批量执行语句，如果上下文有事务，会包含在事务中执行
     *
     * @param writeStatements
     * @param ids
     */
    private void executeWriteStatements(List<BatchWriteStatement> writeStatements, Object[] ids) {
        if (CollectionUtils.isEmpty(writeStatements)) {
            return;
        }

        if (this.transactionTemplate != null && this.needExplicitTransaction(writeStatements)) {
            this.transactionTemplate.setReadOnly(false);

            this.transactionTemplate.execute(status -> {
                this.executeWriteStatements0(writeStatements);
                this.cacheRemoveHandler.accept(ids);

                return null;
            });
        } else {
            this.executeWriteStatements0(writeStatements);
            this.cacheRemoveHandler.accept(ids);
        }
    }

    private void executeWriteStatements0(List<BatchWriteStatement> writeStatements) {
        for (BatchWriteStatement writeStatement : writeStatements) {
            String sql = writeStatement.getSql();
            List<Object[]> batchArguments = writeStatement.getBatchArguments();
            int[] argumentTypes = writeStatement.getArgumentTypes();

            int[] updateCounts = this.sqlExecutor.batchUpdate(sql, batchArguments, argumentTypes);

            this.optimisticLockingValidator.checkUpdateCounts(writeStatement, updateCounts);
        }
    }

    /**
     * 从writeCommands字段中获取命令，将其组装成批量DML语句
     *
     * @return
     */
    private List<BatchWriteStatement> genBatchWriteStatements() {
        // TreeMap,按key:table的顺序迭代
        Map<String, List<BatchWriteStatement>> statementsMap = new TreeMap<>();

        // writeCommands以表为单位分组
        for (DataTableWriteCommandWrapper writeCommand : writeCommands.values()) {
            // 同一张表的各个语句按主键排序
            writeCommand.sortByPkValues();
            TableMeta tableMeta = writeCommand.getTableMeta();

            String tableName = tableMeta.getName();
            List<BatchWriteStatement> statements = statementsMap.get(tableName);
            if (statements == null) {
                statements = new ArrayList<>();
                statementsMap.put(tableName, statements);
            }

            // 获取当前表的删除语句
            BatchWriteStatement deleteStatement = sqlParser.getDeleteStatement(tableMeta,
                    writeCommand.getDeletePkValues());
            if (deleteStatement != null) {
                statements.add(deleteStatement);

                // 添加行删除事件对象
                this.addRowDeleteEvent(tableMeta, writeCommand.getDeletePkValues());
            }

            // 获取当前表的新增语句，因为每句语句新增的字段有可能不一样，所以这里返回的是list
            List<BatchWriteStatement> insertStatements = sqlParser.getInsertStatements(tableMeta,
                    writeCommand.getInsertCommands());
            if (!CollectionUtils.isEmpty(insertStatements)) {
                statements.addAll(insertStatements);

                // 添加行新增事件对象
                this.addRowInsertEvent(tableMeta, writeCommand.getInsertCommands());
            }

            // 获取当前表的更新语句，因为每句语句更新的字段有可能不一样，所以这里返回的是list
            List<BatchWriteStatement> updateStatements = sqlParser.getUpdateStatements(tableMeta,
                    writeCommand.getUpdateCommands());
            if (!CollectionUtils.isEmpty(updateStatements)) {
                statements.addAll(updateStatements);

                // 添加行更新事件对象
                this.addRowUpdateEvent(tableMeta, writeCommand.getUpdateCommands());
            }
        }

        return statementsMap.values().stream().flatMap(sts -> sts.stream()).collect(Collectors.toList());
    }

    private Object[] getAddedObjectIds(IDataObjectMeta objectMeta, Object[] dataObjects) {
        IDataField pkField = objectMeta.getPkField();
        Object[] ids = Stream.of(dataObjects).filter(o -> !objectMeta.isFromDB(o)).map(o -> pkField.getValue(o))
                .toArray();
        return ids;
    }

    private DataTableWriteCommandWrapper getDataTableWriteStatementWrapper(TableMeta tableMate) {
        String tableName = tableMate.getName();
        DataTableWriteCommandWrapper commandWrapper = writeCommands.get(tableName);
        if (commandWrapper == null) {
            commandWrapper = new DataTableWriteCommandWrapper(tableMate);
            writeCommands.put(tableName, commandWrapper);
        }

        return commandWrapper;
    }

    /**
     * 获取状态为"修改过"的数据id
     *
     * @param objectMeta
     * @param dataObjects
     * @return
     */
    private Object[] getModifiedObjectIds(IDataObjectMeta objectMeta, Object[] dataObjects) {
        IDataField pkField = objectMeta.getPkField();
        // 只有修改过的对象才需要从缓存中清除
        Object[] ids = Stream.of(dataObjects).filter(o -> DataContractHelper.isChanged(objectMeta, o))
                .map(o -> pkField.getValue(o)).toArray();
        return ids;
    }

    private Long getTimeStamp() {
        IDataField timestampField = objectMeta.getTimestampField();
        if (timestampField == null) {
            return null;
        }

        long ts = System.currentTimeMillis();

        return TimestampType.Mills.equals(objectMeta.getTimestampType()) ? ts : ts / 1000;
    }

    /**
     * 是否需要显示开启事务
     *
     * @param writeStatements
     * @return
     */
    private boolean needExplicitTransaction(List<BatchWriteStatement> writeStatements) {
        // 已经在事务中，不需要再开启事务
        if (TransactionHelper.inTransactionScope()) {
            return false;
        }

        return true;

        // 即使只有一句，也要手动开启事务，因为我们需要在事务提交前确保数据从缓存清除
//		if (writeStatements.size() > 1) {
//			return true;
//		}
//
//		return writeStatements.get(0).getBatchArguments().size() > 1;
    }

    /**
     * 设置新的时间戳，返回之前的时间戳
     *
     * @param objectMeta
     * @param dataObject
     * @param isfromDB
     */
    private Long refreshTimestamp(IDataObjectMeta objectMeta, Object dataObject, boolean isfromDB) {
        IDataField timestampField = objectMeta.getTimestampField();
        if (timestampField == null) {
            return null;
        }

        Long pre = (Long) timestampField.getValue(dataObject);
        if (!isfromDB && pre != null) {
            // 对于新增的数据，如果时间戳已经有值，就不再对其进行赋值
            return pre;
        }

        timestampField.setValue(dataObject, this.saveTimestamp);

        return pre;
    }

    class DataObjectSaveAnalyzer implements IDataObjectIterateHandler {

        @Override
        public void handleDataCollection(DataCollectionIterateEvent event) {
            ICollectionField collectionField = event.getCollectionField();
            Object parentObject = event.getParentObject();

            // 分析集合，找出被删除对象的id,生成删除命令
            analysisDataCollection(collectionField, parentObject);
        }

        @Override
        public void handleDataObjects(DataObjectIterateEvent event) {
            IDataObjectMeta objectMeta = event.getObjectMeta();
            List<Object> curObjects = event.getDataObjects();
            List<Object> parentObjects = event.getParentObjects();

            if (!CollectionUtils.isEmpty(parentObjects)) {
                int i = 0;
                for (Object dataObject : curObjects) {
                    analysisDataObject(objectMeta, parentObjects.get(i), dataObject);
                    i++;
                }
            } else {
                for (Object dataObject : curObjects) {
                    analysisDataObject(objectMeta, null, dataObject);
                }
            }
        }

        @Override
        public boolean iterateDataObjectField(IDataObjectField dataObjectField) {
            if (dataObjectField instanceof IReferenceField) {
                return false;
            }

            return true;
        }

    }

}
